﻿using System.Windows.Controls;
using Microsoft.Practices.Unity;
using System;
using ThemeLoader;
using System.Windows;
using Core.Infrastructure.EventArgs;
using System.Collections.Generic;
using System.Reflection;

namespace Core.Infrastructure.ScreenManager
{
    public abstract class ScreenBase : IScreen
    {
        private Func<Control> viewFactory;
        private Control view;
        private object viewModel;

        public string StyleKey { get; set; }
        public IResourceProvider ResourceProvider { get; set; }

        public object ViewModel 
        {
            get
            {
                return viewModel;
            }
            set
            {
                viewModel = value;
                UpdateCoupling();
            }
        }

        public Control View 
        {
            get
            {
                if (view == null)
                {
                    RecreateView();
                }

                return view;
            }
        }

        public abstract bool CanEnter();
        public abstract bool CanLeave();
        public abstract void CleanUp();

        public ScreenBase(IResourceProvider resourceProvider)
        {
            this.ResourceProvider = resourceProvider;
        }

        public void RegisterView<T>() where T:Control, new() 
        {
            this.viewFactory = () => new T();
        }

        public virtual void RecreateView()
        {
            if (viewFactory == null)
            {
                throw new InvalidOperationException("Call RegisterView first");
            }

            this.view = viewFactory();

            if (!String.IsNullOrEmpty(StyleKey))
            {
                var style = (Style)this.ResourceProvider.GetResource(StyleKey);
                view.Style = style;
            }

            UpdateCoupling();
        }

        public void UpdateCoupling()
        {
            if (view != null && viewModel != null)
            {
                this.view.DataContext = ViewModel;
            }
        }

        public void Activate(ScreenEventArgs args)
        {
            if (args == null || viewModel == null)
            {
                return;
            }

            var viewModelType = viewModel.GetType();
            var interfaces = viewModelType.GetInterfaces();

            if (args.NavigationArgs == null)
            {
                HandleOrdinaryNavigation(interfaces);
            }
            else
            {
                HandleGenericNavigation(interfaces, args.NavigationArgs);   
            }  
        }

        private void HandleOrdinaryNavigation(IEnumerable<Type> viewModelInterfaces)
        {
            foreach (var @interface in viewModelInterfaces)
            {
                if (IsTypeMatch(@interface, typeof(INavigable)))
                {
                    @interface.InvokeMember("OnNavigate", BindingFlags.InvokeMethod, null, viewModel, null);
                }
            }
        }

        private void HandleGenericNavigation(IEnumerable<Type> viewModelInterfaces, object navigationArgs)
        {
            var argsType = navigationArgs.GetType();  

            foreach (var @interface in viewModelInterfaces)
            {
                var genericArguments = @interface.GetGenericArguments();

                if (IsGenericNavigable(@interface))
                {
                    var genericArgument = genericArguments[0];
                    if (IsTypeMatch(genericArgument, argsType))
                    {
                        @interface.InvokeMember("OnNavigate", BindingFlags.InvokeMethod, null, viewModel, new object[] { navigationArgs });
                        return;
                    }
                }
            }

            throw new ArgumentException(String.Format("There is no generic navigable for type: {0}", argsType));
        }

        private static bool IsGenericNavigable(Type @interface)
        {
            var genericNavigableType = typeof(INavigable<>);
            var genericArguments = @interface.GetGenericArguments();

            return @interface.IsGenericType
                   && genericArguments.Length == 1
                   && IsTypeMatch(genericNavigableType, @interface);
        }

        private static bool IsTypeMatch(Type t1, Type t2)
        {
            return t1.Name == t2.Name;
        }
    }
}
